/**
 * Lightweight wrapper around the SVG DOM
 */

var svgns = "http://www.w3.org/2000/svg";
var xlinkns = "http://www.w3.org/1999/xlink";


/*** Utilities ***/

/* Calls func for each element in the array. */
function forEach(array, func) {
    var len = array.length;
    for (var i=0; i<len; i++) {
	func(array[i]);
    }
}

// Technique from http://ajaxcookbook.org/javascript-inheritance/
function inherit(subclass, superclass) {
  var c = function() {};
  c.prototype = superclass.prototype;
  subclass.prototype = new c();
}

function DOM(x) {
  if (x.dom) return x.dom;
  else if (x.ownerDocument) return x;
  else logError('Expecting to get a DOM element.');
}


/*** Directions ***/
function Direction(north, east) {
  this.north = north;
  this.east = east;
}
Direction.prototype.flipped = function() {
  return new Direction(-this.north, -this.east);
}
/*
 * CCW angle from the +east (+x) axis
 */
Direction.prototype.mathAngle = function() {
  return Math.atan2(this.north, this.east)*180/Math.PI;
}
/*
 * CW angle from North
 */
Direction.prototype.compassAngle = function() {
  return (Math.atan2(this.east, this.north)*180/Math.PI + 360) % 360;
}
NORTH = new Direction(1, 0);
EAST = new Direction(0, 1);
SOUTH = new Direction(-1, 0);
WEST = new Direction(0, -1);


/*** DOM utilities ***/
function textNode(contents) {
	return document.createTextNode(contents);
}



/** Object-oriented SVG DOM element.
 * 
 * @param {Object} name
 * @param {Object} attribs
 */
function SVGElem(name, attribs) {
  this.dom = document.createElementNS(svgns, name);
  for (var attr in attribs) {
    this.dom.setAttributeNS(null, attr, attribs[attr]);
  }
	this.name = null;
  this.children = [];
  this.parent = null;
}
SVGElem.prototype.appendChild = function(node) {
  try {
    this.children.push(node);
    this.dom.appendChild(DOM(node));
    node.parent = this;
  } catch (e) {
		console.error('Error adding '+node);
  }
}
SVGElem.prototype.setAttributeNS = function(ns, name, val) {
  this.dom.setAttributeNS(ns, name, val);
}
SVGElem.prototype.setAttribute = function(name, val) {
  this.dom.setAttributeNS(null, name, val);
}
SVGElem.prototype.__defineGetter__('firstChild', function() {
	return this.children[0];
});
SVGElem.prototype.add = function (name, elem) {
  if (this[name])
    console.error('Trying to add another element named %s!', name);
  this[name] = elem;
	elem.name = name;
  this.appendChild(elem);
}

/*SVGElem.prototype.focus = function() {
	this.dom.focus();
}*/

/*for each (var method in ['focus']) {
	SVGElem.prototype[method] = function() {
		this.dom[method].apply(this.dom, arguments);
	};
}*/



/**
 * 
 * @param {Object} pos
 * @param {Object} origin cardinal direction of the text origin relative to the text
 */
function SVGText(pos, origin) {
  var anchor;
  if (origin.east > 0) anchor = 'end';
  else if (origin.east < 0) anchor = 'start';
  else anchor = 'middle';
  
  var baseline;
  if (origin.north > 0) baseline = 'text-before-edge'; // that means 'top'.
  else if (origin.north < 0) baseline = 'text-after-edge';
  else baseline = 'central';
  
  SVGElem.call(this, 'text', {
    x: pos.x, y: pos.y,
    'dominant-baseline': baseline,
    'text-anchor': anchor
  });

  this.text = textNode('undef');
  this.dom.appendChild(this.text);

  this.pos = pos;
}
inherit(SVGText, SVGElem);
SVGText.prototype.setText = function(txt) {
  this.text.data = txt;
};


/*** Root SVG Element ***/
function RootSVGElem(id) {
	SVGElem.call(this, 'svg', {
		id: id,
    version: "1.1",
    baseProfile: "full",
    width: "100%",
    height: "100%"
  });
}
inherit(RootSVGElem, SVGElem);
RootSVGElem.prototype.addStylesheet = function(contents) {
	var stylesheet = new SVGElem('style', {type: 'text/css'});
  this.appendChild(stylesheet);
  stylesheet.appendChild(textNode(contents));
}




/***
* Event handling
*
* various parts of this are from
* http://ajaxcookbook.org/event-handling-memory-leaks/
*/

var __eventListeners = [];

function addListener(instance, eventName, listener) {
    var listenerFn = listener;
    if (instance.addEventListener) {
        instance.addEventListener(eventName, listenerFn, false);
    } else if (instance.attachEvent) {
        listenerFn = function() {
            listener(window.event);
        }
        instance.attachEvent("on" + eventName, listenerFn);
    } else {
        throw new Error("Event registration not supported");
    }
    var event = {
        instance: instance,
        name: eventName,
        listener: listenerFn
    };
    __eventListeners.push(event);
    return event;
}

function removeListener(event) {
    var instance = event.instance;
    if (instance.removeEventListener) {
        instance.removeEventListener(event.name, event.listener, false);
    } else if (instance.detachEvent) {
        instance.detachEvent("on" + event.name, event.listener);
    }
    for (var i = 0; i < __eventListeners.length; i++) {
        if (__eventListeners[i] == event) {
            __eventListeners.splice(i, 1);
            break;
        }
    }
}

function unregisterAllEvents() {
    while (__eventListeners.length > 0) {
        removeListener(__eventListeners[0]);
    }
}

function addLoadEvent(func) {
    return addListener(window, "load", func);
}

/*** Wheel events ***/
$wheelFuncs = new Array();

// add a function that will be called with a 'delta' on each wheel event.
function addWheelEvent(func) {
	if ($wheelFuncs.length == 0) {
		// Set up wheel handling.
		if (window.addEventListener)
			/** DOMMouseScroll is for mozilla. */
			window.addEventListener('DOMMouseScroll', $wheel, false);
		/** IE/Opera. */
		window.onmousewheel = document.onmousewheel = $wheel;
	}
	$wheelFuncs.push(func);
}

/** Event handler for mouse wheel event.
 * From: http://adomas.org/javascript-mouse-wheel/
 */
function $wheel(event){
  var delta = 0;
  if (!event) /* For IE. */
    event = window.event;
  if (event.wheelDelta) { /* IE/Opera. */
    delta = event.wheelDelta/120;
    /** In Opera 9, delta differs in sign as compared to IE.
     */
    if (window.opera)
      delta = -delta;
  } else if (event.detail) { /** Mozilla case. */
    /** In Mozilla, sign of delta is different than in IE.
     * Also, delta is multiple of 3.
     */
    delta = -event.detail/3;
  }
  /** If delta is nonzero, handle it.
   * Basically, delta is now positive if wheel was scrolled up,
   * and negative, if wheel was scrolled down.
   */
  if (delta)
    for each (var func in $wheelFuncs) func(delta);

  // Prevent default actions caused by mouse wheel.
  if (event.preventDefault)
    event.preventDefault();
  event.returnValue = false;
}


/*var _elems = {}*/

/**
* Return the JavaScript object corresponding to the SVG element el.
*/
/*function $(el) {
    if (el instanceof String) {
	if (_elems[el]) {
	    return _elems[el];
	} else {
	}
    }
}*/
